/*****************************************************************************
 *   Copyright (C) 2008 John Schember <john@nachtimwald.com>                 *
 *                                                                           *
 *   This file is part of niwbillmanager.                                    *
 *                                                                           *
 *   niwbillmanager is free software: you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License as          *
 *   published by the Free Software Foundation, either version 3 of the      *
 *   License, or (at your option) any later version.                         *
 *                                                                           *
 *   niwbillmanager is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
 *   GNU General Public License for more details.                            *
 *                                                                           *
 *   You should have received a copy of the GNU General Public License       *
 *   along with niwbillmanager. If not, see                                  *
 *   <http://www.gnu.org/licenses/>.                                         *
 *****************************************************************************/

#include <QtDebug>

#include <QFile>
#include <QFileInfo>
#include <QHashIterator>

#include <niwbillmanager.h>

#include "backendmanager.h"
#include "datastore.h"

Q_IMPORT_PLUGIN(backend_plaintext)

NiwBillManager::NiwBillManager()
{
    m_backendManager = new BackendManager();
    m_dataStore = new DataStore();

    m_errorMessage = "";
}

NiwBillManager::~NiwBillManager()
{
    delete m_dataStore;
    delete m_backendManager;
}

bool NiwBillManager::isBackendLoaded()
{
    if (m_backendManager->getCurrentBackend()) {
        return true;
    }

    m_errorMessage = "No backend loaded.";
    return false;
}

void NiwBillManager::newCollection()
{
    m_dataStore->clearCollection();
}

bool NiwBillManager::loadCollection(const QString &location,
            const QString &username, const QString &loginname,
            const QString &password)
{
    newCollection();

    if (m_backendManager->getCurrentBackend()->openCollection(location,
        username, loginname, password))
    {
        if (m_backendManager->getCurrentBackend()->canReadIncremental()) {
            m_backendManager->getCurrentBackend()->startReadNextBillItem();
            while (m_backendManager->getCurrentBackend()->hasMoreBillItems()) {
                BillItem billItem;

                if (m_backendManager->getCurrentBackend()->readNextBillItem(
                    billItem))
                {
                    m_dataStore->addBillItem(billItem, true);
                }
            }
        }
        else {
            QSet<BillItem> billItems;

            m_backendManager->getCurrentBackend()->readAllBillItems(billItems);
            Q_FOREACH (BillItem billItem, billItems) {
                m_dataStore->addBillItem(billItem, true);
            }
        }

        m_backendManager->getCurrentBackend()->closeCollection();
        return true;
    }

    m_errorMessage = m_backendManager->getCurrentBackend()->
        getLastErrorMessage();
    return false;
}

bool NiwBillManager::saveCollection(const QString &location,
            const QString &username, const QString &loginname,
            const QString &password)
{
    if (m_backendManager->getCurrentBackend()->openCollection(location,
        username, loginname, password))
    {
        if (m_backendManager->getCurrentBackend()->canSaveTransactional()) {
            QHash<QString, TransactionType::TransactionTypes>
                shortTransactions;

            shortTransactions = createShortTransactionLog();

            // Get the billitem associated with the id, save the change
            // to disk via the backend.
            QHashIterator<QString, TransactionType::TransactionTypes>
                shortTransactionIterator(shortTransactions);
            while (shortTransactionIterator.hasNext()) {
                shortTransactionIterator.next();

                Transaction transaction;
                BillItem billItem;

                transaction.setTransactionType(
                    shortTransactionIterator.value());
                m_dataStore->getBillItemById(shortTransactionIterator.key(),
                    billItem);
                transaction.setBillItem(billItem);

                m_backendManager->getCurrentBackend()->saveChange(transaction);
            }
        }
        else if (m_backendManager->getCurrentBackend()->
            canSaveAllIncremental())
        {
            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                BillItem billItem;
                m_dataStore->getNextBillItem(billItem);
                m_backendManager->getCurrentBackend()->saveBillItem(billItem);
            }
        }
        else {
            QSet<BillItem> billItems;

            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                BillItem billItem;
                m_dataStore->getNextBillItem(billItem);
                billItems.insert(billItem);
            }
            m_backendManager->getCurrentBackend()->saveAllBillItems(billItems);
        }

        m_backendManager->getCurrentBackend()->closeCollection();
        m_dataStore->clearRawTransactionLog();
        return true;
    }

    m_errorMessage = m_backendManager->getCurrentBackend()->
        getLastErrorMessage();
    return false;
}

bool NiwBillManager::saveAsCollection(const QString &location,
            const QString &username, const QString &loginname,
            const QString &password)
{
    // Remove any data in the file so we can save into it.
    if (QFileInfo(location).exists()) {
        QFile(location).resize(0);
    }

    if (m_backendManager->getCurrentBackend()->openCollection(location,
        username, loginname, password))
    {
        if (m_backendManager->getCurrentBackend()->canSaveTransactional()) {
            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                Transaction transaction;
                BillItem billItem;

                m_dataStore->getNextBillItem(billItem);

                transaction.setTransactionType(TransactionType::INSERT);
                transaction.setBillItem(billItem);

                m_backendManager->getCurrentBackend()->saveChange(transaction);
            }
        }
        else if (m_backendManager->getCurrentBackend()->
            canSaveAllIncremental())
        {
            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                BillItem billItem;
                m_dataStore->getNextBillItem(billItem);
                m_backendManager->getCurrentBackend()->saveBillItem(billItem);
            }
        }
        else {
            QSet<BillItem> billItems;

            m_dataStore->startGetAllBillItems();
            while (m_dataStore->hasMoreBillItems()) {
                BillItem billItem;
                m_dataStore->getNextBillItem(billItem);
                billItems.insert(billItem);
            }
            m_backendManager->getCurrentBackend()->saveAllBillItems(billItems);
        }

        m_backendManager->getCurrentBackend()->closeCollection();
        m_dataStore->clearRawTransactionLog();
        return true;
    }

    m_errorMessage = m_backendManager->getCurrentBackend()->
        getLastErrorMessage();
    return false;
}

bool NiwBillManager::shouldSave()
{
    m_dataStore->startGetRawTransactionLog();
    if (m_dataStore->hasMoreRawTransactions()) {
        return true;
    }

    return false;
}

QStringList NiwBillManager::getAvaliableBackends()
{
    return m_backendManager->getAvaliableBackends();
}

bool NiwBillManager::setBackend(QString backendName)
{
    return m_backendManager->setCurrentBackend(backendName);
}

QString NiwBillManager::getBackendName()
{
    return m_backendManager->getCurrentBackend()->getName();
}

QString NiwBillManager::getBackendDescription()
{
    return m_backendManager->getCurrentBackend()->getDescription();
}

QString NiwBillManager::getBackendDescription(QString backendName)
{
    return m_backendManager->getBackendDescription(backendName);
}

bool NiwBillManager::isBackendRemote()
{
    return m_backendManager->getCurrentBackend()->isRemote();
}

bool NiwBillManager::isBackendRemote(QString backendName)
{
    return m_backendManager->isBackendRemote(backendName);
}

QSet<QString> NiwBillManager::getAllTags()
{
    return m_dataStore->getAllTags();
}

QSet<QString> NiwBillManager::getTags(QString billId)
{
    return m_dataStore->getTags(billId);
}

QSet<BillItem> NiwBillManager::getBillItemsFromTags(QSet<QString> tags)
{
    QSet<BillItem> billItems;

    m_dataStore->startGetBillItemsFromTags(tags);
    while (m_dataStore->hasMoreBillItems()) {
        BillItem billItem;
        m_dataStore->getNextBillItem(billItem);
        billItems.insert(billItem);
    }

    return billItems;
}

void NiwBillManager::startGetBillItemsFromTags(QSet<QString> tags)
{
    m_dataStore->startGetBillItemsFromTags(tags);
}

QSet<BillItem> NiwBillManager::getBillItemsWithoutTags()
{
    QSet<BillItem> billItems;

    m_dataStore->startGetBillItemsWithoutTags();
    while (m_dataStore->hasMoreBillItems()) {
        BillItem billItem;
        m_dataStore->getNextBillItem(billItem);
        billItems.insert(billItem);
    }

    return billItems;
}

void NiwBillManager::startGetBillItemsWithoutTags()
{
    m_dataStore->startGetBillItemsWithoutTags();
}

QString NiwBillManager::newBillItem(BillItem billItem)
{
    return m_dataStore->addBillItem(billItem);
}

bool NiwBillManager::modifyBillItem(BillItem billItem)
{
    return m_dataStore->modifyBillItem(billItem);
}

bool NiwBillManager::deleteBillItem(QString billId)
{
    return m_dataStore->deleteBillItem(billId);
}

bool NiwBillManager::getBillItemById(QString billId, BillItem &billItem)
{
    return m_dataStore->getBillItemById(billId, billItem);
}

QSet<BillItem> NiwBillManager::getAllBillItems()
{
    QSet<BillItem> billItems;

    m_dataStore->startGetAllBillItems();
    while (m_dataStore->hasMoreBillItems()) {
        BillItem billItem;
        m_dataStore->getNextBillItem(billItem);
        billItems.insert(billItem);
    }

    return billItems;
}

void NiwBillManager::startGetAllBillItems()
{
    m_dataStore->startGetAllBillItems();
}

void NiwBillManager::startSearchBillItemsAnd(QSet<QString> query,
    bool amountDue, bool autoPay, bool nonAutoPay,
    bool dateDue, bool payee, bool paymentLocation,
    bool paymentMethod, bool paymentRecurringPeriod,
    bool recurringOnly, bool paid, bool unpaid,
    bool name, bool notes, bool tags)
{
    m_dataStore->startGetBillItems(query, QueryType::AND, amountDue, autoPay,
        nonAutoPay, dateDue, payee, paymentLocation, paymentMethod,
        paymentRecurringPeriod, recurringOnly, paid, unpaid, name, notes,
        tags);
}

void NiwBillManager::startSearchBillItemsOr(QSet<QString> query,
    bool amountDue, bool autoPay, bool nonAutoPay,
    bool dateDue, bool payee, bool paymentLocation,
    bool paymentMethod, bool paymentRecurringPeriod,
    bool recurringOnly, bool paid, bool unpaid,
    bool name, bool notes, bool tags)
{
    m_dataStore->startGetBillItems(query, QueryType::OR, amountDue, autoPay,
        nonAutoPay, dateDue, payee, paymentLocation, paymentMethod,
        paymentRecurringPeriod, recurringOnly, paid, unpaid, name, notes,
        tags);
}

bool NiwBillManager::hasMoreBillItems()
{
    return m_dataStore->hasMoreBillItems();
}

bool NiwBillManager::getNextBillItem(BillItem &billItem)
{
    return m_dataStore->getNextBillItem(billItem);
}

bool NiwBillManager::canUndoChange()
{
    return m_dataStore->canUndoChange();
}

bool NiwBillManager::canRedoChange()
{
    return m_dataStore->canRedoChange();
}

bool NiwBillManager::undoChange()
{
    Transaction transaction;

    return m_dataStore->undoChange(transaction);
}

bool NiwBillManager::undoChange(Transaction &transaction)
{
    return m_dataStore->undoChange(transaction);
}

bool NiwBillManager::redoChange()
{
    Transaction transaction;

    return m_dataStore->redoChange(transaction);
}


bool NiwBillManager::redoChange(Transaction &transaction)
{
    return m_dataStore->redoChange(transaction);
}

QString NiwBillManager::getLastErrorMessage()
{
    return m_errorMessage;
}

QHash<QString, TransactionType::TransactionTypes>
    NiwBillManager::createShortTransactionLog()
{
    QHash<QString, TransactionType::TransactionTypes> shortTransactions;

    m_dataStore->startGetRawTransactionLog();

    // Shorten the transaction list down to the changes that need to
    // be written to disk.
    while (m_dataStore->hasMoreRawTransactions()) {
        ShortTransaction shortTransaction;
        shortTransaction = m_dataStore->getNextRawTransaction();

        // Check if there is already a transaction for the bill.
        if (shortTransactions.contains(shortTransaction.getBillId())) {
            // If the transaction in the list is an INSERT, and the
            // TransactionType of the next item is a DELETE, then
            // then remove the bill from the list.
            if (shortTransactions.value(shortTransaction.getBillId())
                == TransactionType::INSERT
                && shortTransaction.getTransactionType()
                == TransactionType::DELETE)
            {
                shortTransactions.remove(shortTransaction.getBillId());
            }
            // If the transaction in the list is a DELETE, and the
            // TransactionType of the next item is an INSERT, then
            // change the type to and UPDATE. UPDATE to be save that no
            // data is lost. This situation can arrise from an undo.
            else if(shortTransactions.value(shortTransaction.getBillId())
                == TransactionType::DELETE
                && shortTransaction.getTransactionType()
                == TransactionType::INSERT)
            {
                shortTransactions.insert(shortTransaction.getBillId(),
                    TransactionType::UPDATE);
            }
        }
        // If there is no id in the list add the id and the
        // TransactionType.
        else {
            shortTransactions.insert(shortTransaction.getBillId(),
                shortTransaction.getTransactionType());
        }
    }

    return shortTransactions;
}
